#ifndef BRUSH_H
#define BRUSH_H

class Brush
{
	//This ds is reserved for developing complicated brushs. 
	enum BrushType {}; 
public:
	//Brush(): brush_size(0.0005), color() {}
	Brush(): brush_size(0.0025), color(0,0,0), winx(WINDOW_WIDTH /2 ), winy(WINDOW_HEIGHT /2),pixel_size(130) {}
	virtual ~ Brush() {}
	virtual void paint () {}
	virtual inline float get_size() const {return brush_size ;}
	virtual inline void set_size(float size) {this->brush_size = size ;}
	virtual inline float get_pixel_size() const {return pixel_size ;}
	virtual inline void set_pixel_size(float size) {this->pixel_size = size ;}
	virtual inline void set_winx (float x) {this->winx = x ;}
	virtual inline void set_winy (float y) {this->winy = y ;}
	virtual inline float get_winx () const {return this->winx  ;}
	virtual inline float get_winy () const {return this->winy  ;}
	virtual inline void set_color (int_RGB & c ) {this->color = c;}
	virtual inline int_RGB  get_color () const { return this->color ;}
	virtual void draw ()
	{
		draw(Vector2(winx, winy));
	}
	
	virtual void draw (Vector2 mousePos) 
	{
		getBrushCoordEnv();
		Vector3 objPos = mappingWin2Obj(Vector3(mousePos.x, mousePos.y, .99f), bModelViewMatrix, bProjectionMatrix, bViewPort);
		//Set the brush transparent
		glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
		glEnable(GL_BLEND);
		glLineWidth(3) ;
		glBegin(GL_LINE_LOOP) ;
		glColor4f(0.75,0.75,0.75,0.75);
		for (float angle = 0.0f ; angle <= 3.1415926f * 2.0f + 0.1f ; angle += 3.1415926f * 2.0f/ 50.0f)
		{
			glVertex3f(objPos.x + cos(angle) * brush_size,  objPos.y + sin(angle) * brush_size, 0.0f);
		}
		glEnd();
		glLineWidth(1) ;
		/*
		glBegin(GL_TRIANGLE_FAN) ;
		glColor4f(0.75,0.75,0.75,0.75);
		glVertex3f(objPos.x, objPos.y, 0.0f);
		for (float angle = 0.0f ; angle <= 3.1415926f * 2.0f + 0.1f ; angle += 3.1415926f * 2.0f/ 50.0f)
		{
			glVertex3f(objPos.x + cos(angle) * brush_size,  objPos.y + sin(angle) * brush_size, 0.0f);
		}
		glEnd();
		*/
		glDisable(GL_BLEND);
		update_brush_pixel_size();
	}

	void getObjCoordEnv ()
	{
		glGetDoublev (GL_MODELVIEW_MATRIX, this->modelViewMatrix);
		glGetDoublev (GL_PROJECTION_MATRIX, this->projectionMatrix);
		glGetIntegerv(GL_VIEWPORT, this->viewPort);	
	}
	void getBrushCoordEnv()
	{
		glGetDoublev (GL_MODELVIEW_MATRIX, this->bModelViewMatrix);
		glGetDoublev (GL_PROJECTION_MATRIX, this->bProjectionMatrix);
		glGetIntegerv(GL_VIEWPORT, this->bViewPort);	
	}
	bool isInBrush(Vector3 & objCoord)
	{
		float ratio ;
		return isInBrush(objCoord, ratio);
	}
	bool isInBrush(Vector3 & objCoord, float & ratio)
	{
		Vector3 winCoord = mappingObj2Win(objCoord, this->modelViewMatrix, 
			this->projectionMatrix, this->viewPort);
		ratio = 0;
		float distance = sqrt((winCoord.x - winx) * (winCoord.x - winx)
					+ (winCoord.y - winy) * (winCoord.y - winy));
		if (  
			distance <= pixel_size )
		{
			ratio = distance / pixel_size; 
			return true ;
		}
		return false ;
	}
	inline bool isVisible(Point point[3])
	{
		Point3 p[3];
		for (int i = 0;i < 3; ++i)
		{
			p[i] = mappingObj2Win(point[i], this->modelViewMatrix, this->projectionMatrix, this->viewPort) ;

		}
		Vector3 v1 = p[1]-p[0];
		Vector3 v2 = p[2]-p[0];
		return  (v1^v2).z < 0;
		//return (v3.z > 0);
	}
	inline Vector3 mappingObj2Win (Vector3 & objCoord)
	{
		return mappingObj2Win(objCoord, modelViewMatrix, projectionMatrix, viewPort);
	}

	void pick_ray(Point3 & p1, Point3 & p2)
	{
		pick_ray (this->winx, this->winy, p1, p2);
	}
protected:
	virtual void update_brush_pixel_size ()
	{
		unsigned x = WINDOW_WIDTH / 2;
		unsigned y = WINDOW_HEIGHT / 2;
		Vector3 objPos = mappingWin2Obj(Vector3(x, y, .99f), bModelViewMatrix, bProjectionMatrix, bViewPort);
		objPos.x += this->brush_size ;
		Vector3 winPos = mappingObj2Win(objPos,  bModelViewMatrix, bProjectionMatrix, bViewPort);
		this->pixel_size = winPos.x - x; 
	}
	void pick_ray(unsigned winx, unsigned winy, Point3 & p1, Point3 & p2)
	{
		// p1 is on z-near
		// p2 is on z-far
		p1  = mappingWin2Obj(Vector3 (winx, winy, 0.0f), modelViewMatrix, projectionMatrix, viewPort);
		p2  = mappingWin2Obj(Vector3 (winx, winy, 1.0f), modelViewMatrix, projectionMatrix, viewPort);
	}
	inline Vector3 mappingObj2Win (Vector3 & objCoord, double * modelViewMatrix, double * projectionMatrix, int * viewPort)
	{
		double winx, winy, winz ;
		gluProject(objCoord.x, objCoord.y, objCoord.z, modelViewMatrix, projectionMatrix, viewPort, & winx, & winy, & winz );
		winy = WINDOW_HEIGHT - winy ;
		return Vector3 (float(winx), float(winy), float(winz));
	}

	inline Vector3 mappingWin2Obj (Vector3 & winPos,  double * mvMatrix, double * pMatrix, int * vp)
	{
		double x, y, z;
		gluUnProject(winPos.x, WINDOW_HEIGHT - winPos.y, winPos.z, mvMatrix, pMatrix, vp, 
			& x, &y , &z);
		return Vector3 (x,y,z);
	}




public:
	//Object View Matrix
	double modelViewMatrix[16] ;
	double projectionMatrix[16];
	int viewPort[4];

	//brush View Matrix 
	double bModelViewMatrix[16] ;
	double bProjectionMatrix[16];
	int bViewPort[4];

public:
	float brush_size ;
	float pixel_size ; // Brush pixel size on the screen 
	int_RGB color ;
	BrushType brushType;

	//Brush Window Position ;
	float winx, winy; 

};
#endif